'use client';
import * as React from 'react';
import { warnOnce } from '@mui/x-internals/warning';
import { type PointerGestureEventData } from '@mui/x-internal-gestures/core';
import { type ChartPlugin } from '../../models';
import { type UseChartPolarAxisSignature } from './useChartPolarAxis.types';
import { useSelector } from '../../../store/useSelector';
import { selectorChartDrawingArea } from '../../corePlugins/useChartDimensions/useChartDimensions.selectors';
import { defaultizeAxis } from './defaultizeAxis';
import { selectorChartsInteractionIsInitialized } from '../useChartInteraction';
import {
  selectorChartPolarCenter,
  selectorChartRadiusAxis,
  selectorChartRotationAxis,
} from './useChartPolarAxis.selectors';
import { getSVGPoint } from '../../../getSVGPoint';
import {
  generatePolar2svg,
  generateSvg2polar,
  generateSvg2rotation,
} from './coordinateTransformation';
import { getAxisIndex } from './getAxisIndex';
import { selectorChartSeriesProcessed } from '../../corePlugins/useChartSeries';
import { checkHasInteractionPlugin } from '../useChartInteraction/checkHasInteractionPlugin';

export const useChartPolarAxis: ChartPlugin<UseChartPolarAxisSignature<any>> = ({
  params,
  store,
  seriesConfig,
  svgRef,
  instance,
}) => {
  const { rotationAxis, radiusAxis, dataset } = params;

  if (process.env.NODE_ENV !== 'production') {
    const ids = [...(rotationAxis ?? []), ...(radiusAxis ?? [])]
      .filter((axis) => axis.id)
      .map((axis) => axis.id);
    const duplicates = new Set(ids.filter((id, index) => ids.indexOf(id) !== index));
    if (duplicates.size > 0) {
      warnOnce(
        [
          `MUI X Charts: The following axis ids are duplicated: ${Array.from(duplicates).join(', ')}.`,
          `Please make sure that each axis has a unique id.`,
        ].join('\n'),
        'error',
      );
    }
  }

  const drawingArea = useSelector(store, selectorChartDrawingArea);
  const processedSeries = useSelector(store, selectorChartSeriesProcessed);
  const center = useSelector(store, selectorChartPolarCenter);
  const isInteractionEnabled = useSelector(store, selectorChartsInteractionIsInitialized);
  const { axis: rotationAxisWithScale, axisIds: rotationAxisIds } = useSelector(
    store,
    selectorChartRotationAxis,
  );
  const { axis: radiusAxisWithScale, axisIds: radiusAxisIds } = useSelector(
    store,
    selectorChartRadiusAxis,
  );

  // The effect do not track any value defined synchronously during the 1st render by hooks called after `useChartPolarAxis`
  // As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one
  const isFirstRender = React.useRef(true);
  React.useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return;
    }

    store.set('polarAxis', {
      ...store.state.polarAxis,
      rotation: defaultizeAxis(rotationAxis, dataset, 'rotation'),
      radius: defaultizeAxis(radiusAxis, dataset, 'radius'),
    });
  }, [seriesConfig, drawingArea, rotationAxis, radiusAxis, dataset, store]);

  const svg2rotation = React.useMemo(
    () => generateSvg2rotation({ cx: center.cx, cy: center.cy }),
    [center.cx, center.cy],
  );

  const svg2polar = React.useMemo(
    () => generateSvg2polar({ cx: center.cx, cy: center.cy }),
    [center.cx, center.cy],
  );

  const polar2svg = React.useMemo(
    () => generatePolar2svg({ cx: center.cx, cy: center.cy }),
    [center.cx, center.cy],
  );

  const usedRotationAxisId = rotationAxisIds[0];
  const usedRadiusAxisId = radiusAxisIds[0];

  // Use a ref to avoid rerendering on every mousemove event.
  const mousePosition = React.useRef({
    isInChart: false,
  });

  const hasInteractionPlugin = checkHasInteractionPlugin(instance);

  React.useEffect(() => {
    const element = svgRef.current;
    if (
      !isInteractionEnabled ||
      !hasInteractionPlugin ||
      element === null ||
      params.disableAxisListener
    ) {
      return () => {};
    }

    // Clean the interaction when the mouse leaves the chart.
    const moveEndHandler = instance.addInteractionListener('moveEnd', (event) => {
      if (!event.detail.activeGestures.pan) {
        mousePosition.current.isInChart = false;
        instance.cleanInteraction();
      }
    });
    const panEndHandler = instance.addInteractionListener('panEnd', (event) => {
      if (!event.detail.activeGestures.move) {
        mousePosition.current.isInChart = false;
        instance.cleanInteraction?.();
      }
    });
    const pressEndHandler = instance.addInteractionListener('quickPressEnd', (event) => {
      if (!event.detail.activeGestures.move && !event.detail.activeGestures.pan) {
        mousePosition.current.isInChart = false;
        instance.cleanInteraction?.();
      }
    });

    const gestureHandler = (event: CustomEvent<PointerGestureEventData>) => {
      const srcEvent = event.detail.srcEvent;

      // On touch, we want to allow user to interact with the entire svg area in
      // order to better display the tooltip.
      if (event.detail.srcEvent.pointerType === 'touch') {
        const svgRect = element.getBoundingClientRect();

        if (
          srcEvent.clientX < svgRect.left ||
          srcEvent.clientX > svgRect.right ||
          srcEvent.clientY < svgRect.top ||
          srcEvent.clientY > svgRect.bottom
        ) {
          mousePosition.current.isInChart = false;
          instance.cleanInteraction?.();
          return;
        }

        const svgPoint = getSVGPoint(element, srcEvent);

        mousePosition.current.isInChart = true;
        instance.setPointerCoordinate?.(svgPoint);
        return;
      }

      // On mouse, we want to restrict the interaction to the drawing area and radar circle.

      const svgPoint = getSVGPoint(element, srcEvent);

      // Test if it's in the drawing area
      if (!instance.isPointInside(svgPoint.x, svgPoint.y, event.detail.target)) {
        if (mousePosition.current.isInChart) {
          instance.cleanInteraction?.();
          mousePosition.current.isInChart = false;
        }
        return;
      }

      // Test if it's in the radar circle
      const radiusSquare = (center.cx - svgPoint.x) ** 2 + (center.cy - svgPoint.y) ** 2;
      const maxRadius = radiusAxisWithScale[usedRadiusAxisId].scale.range()[1];

      if (radiusSquare > maxRadius ** 2) {
        if (mousePosition.current.isInChart) {
          instance.cleanInteraction?.();
          mousePosition.current.isInChart = false;
        }
        return;
      }

      mousePosition.current.isInChart = true;
      instance.setPointerCoordinate?.(svgPoint);
    };

    const moveHandler = instance.addInteractionListener('move', gestureHandler);
    const panHandler = instance.addInteractionListener('pan', gestureHandler);
    const pressHandler = instance.addInteractionListener('quickPress', gestureHandler);

    return () => {
      moveHandler.cleanup();
      moveEndHandler.cleanup();
      panHandler.cleanup();
      panEndHandler.cleanup();
      pressHandler.cleanup();
      pressEndHandler.cleanup();
    };
  }, [
    svgRef,
    store,
    center,
    radiusAxisWithScale,
    usedRadiusAxisId,
    rotationAxisWithScale,
    usedRotationAxisId,
    instance,
    params.disableAxisListener,
    isInteractionEnabled,
    svg2rotation,
    hasInteractionPlugin,
  ]);

  React.useEffect(() => {
    const element = svgRef.current;
    const onAxisClick = params.onAxisClick;
    if (element === null || !onAxisClick) {
      return () => {};
    }

    const axisClickHandler = instance.addInteractionListener('tap', (event) => {
      let dataIndex: number | null = null;
      let isRotationAxis: boolean = false;

      const svgPoint = getSVGPoint(element, event.detail.srcEvent);

      const rotation = generateSvg2rotation(center)(svgPoint.x, svgPoint.y);
      const rotationIndex = getAxisIndex(rotationAxisWithScale[usedRotationAxisId], rotation);
      isRotationAxis = rotationIndex !== -1;

      dataIndex = isRotationAxis ? rotationIndex : null; // radius index is not yet implemented.

      const USED_AXIS_ID = isRotationAxis ? usedRotationAxisId : usedRadiusAxisId;
      if (dataIndex == null || dataIndex === -1) {
        return;
      }

      // The .data exist because otherwise the dataIndex would be null or -1.
      const axisValue = (isRotationAxis ? rotationAxisWithScale : radiusAxisWithScale)[USED_AXIS_ID]
        .data![dataIndex];

      const seriesValues: Record<string, number | null | undefined> = {};

      Object.keys(processedSeries)
        .filter((seriesType): seriesType is 'radar' => seriesType === 'radar')
        .forEach((seriesType) => {
          processedSeries[seriesType]?.seriesOrder.forEach((seriesId) => {
            const seriesItem = processedSeries[seriesType]!.series[seriesId];

            seriesValues[seriesId] = seriesItem.data[dataIndex];
          });
        });

      onAxisClick(event.detail.srcEvent, { dataIndex, axisValue, seriesValues });
    });

    return () => {
      axisClickHandler.cleanup();
    };
  }, [
    center,
    instance,
    params.onAxisClick,
    processedSeries,
    radiusAxisWithScale,
    rotationAxisWithScale,
    svgRef,
    usedRadiusAxisId,
    usedRotationAxisId,
  ]);

  return {
    instance: {
      svg2polar,
      svg2rotation,
      polar2svg,
    },
  };
};

useChartPolarAxis.params = {
  rotationAxis: true,
  radiusAxis: true,
  dataset: true,
  disableAxisListener: true,
  onAxisClick: true,
};

useChartPolarAxis.getInitialState = (params) => ({
  polarAxis: {
    rotation: defaultizeAxis(params.rotationAxis, params.dataset, 'rotation'),
    radius: defaultizeAxis(params.radiusAxis, params.dataset, 'radius'),
  },
});
